package utils;

import org.apache.commons.lang.StringUtils;
import play.Logger;
import play.db.jpa.JPABase;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * JavaBean转换工具
 * 可用于将model转换成bean输出，选择输出的字段
 * @author chao
 * @since 2014/9/26
 */
public class BeanUtils {

    /**
     * 将一个对象转换成另一个对象
     * @param beanClass
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T build(Class<T> beanClass, Object obj){
        Class source = obj.getClass();

        if(beanClass.equals(source)){ //如果类型相同，则不进行转换
            return (T) obj;
        }

        T bean;
        try{
            Constructor constructor = beanClass.getConstructor(obj.getClass());
            bean = (T)constructor.newInstance(obj);
        }catch(Exception ignore){
            try{
                bean = beanClass.newInstance();
            }catch (Exception e){
                Logger.error(e, "Error in build");
                return null;
            }

        }

        for(Field field: beanClass.getFields()){
            String name = field.getName();

            //标记这个Field是否被设置
            boolean isSet = false;

            //使用@Method来调用obj的方法来设置bean
            if(field.isAnnotationPresent(Method.class)){
                Method $m = field.getAnnotation(Method.class);
                String methodName = $m.value();
                Class asClass = $m.as();
                if(StringUtils.isEmpty(methodName)){//没有标记方法名
                    Logger.warn("Annotation @Method(\"%s\") does not declear a method, ignore this annation.", methodName);
                }else if(field.getType().equals(List.class)) {
                    try {
                        java.lang.reflect.Method method = source.getMethod(methodName);
                        Object resultObj = method.invoke(obj);
                        if(resultObj instanceof List){
                            field.set(bean, buildList(asClass, (List)resultObj));
                            isSet = true;
                        }else{
                            Logger.error("Error in processing annotation @Method(\"%s\"): Method[%s] does not return same type as field[%s]",methodName, methodName, field.getName());
                        }

                    } catch (Exception e) {
                        Logger.error(e, "Error in processing annotation @Method(\"%s\")",methodName);
                    }
                }else{
                    try {
                        java.lang.reflect.Method method = source.getMethod(methodName);
                        Object resultObj = method.invoke(obj);
                        try{
                            field.set(bean, resultObj);
                        }catch (Exception e){
                            field.set(bean, build(field.getType(), resultObj));
                        }

                        isSet = true;
                    } catch (Exception e) {
                        Logger.error(e, "Error in processing annotation @Method(\"%s\")",methodName);
                    }
                }

            }

            //使用@Link来来设置bean
            if(field.isAnnotationPresent(Link.class)){
                Link $l = field.getAnnotation(Link.class);
                Class<? extends JPABase> modalClass = $l.value();
                String idField = $l.id();
                String fieldName = $l.field();
                Class fieldClass = field.getType();

                try {
                    if(source.getField(idField).get(obj) != null){
                        java.lang.reflect.Method findByIdMethod = modalClass.getMethod("findById", Object.class);
                        Object modelInDb = findByIdMethod.invoke(null, source.getField(idField).get(obj));
                        if(StringUtils.isEmpty(fieldName)){
                            field.set(bean, BeanUtils.build(fieldClass, modelInDb));
                        }else{//如果指定了Field
                            field.set(bean, modalClass.getField(fieldName).get(modelInDb));
                        }
                        isSet = true;
                    }
                } catch (Exception e) {
                    Logger.error(e, "Error in processing annotation @Link");
                }

            }

            //直接将obj的字段设置到bean中
            if(!isSet){
                try{
                    Field sourceField = source.getField(name);
                    if(sourceField != null){
                        field.set(bean, sourceField.get(obj));
                        isSet = true;
                    }
                }catch (Exception e){
                    //Logger.error(e, "Error in BeanUtils");
                }
            }


        }
        return bean;
    }

    /**
     * 批量转换
     * @param beanClass
     * @param objs
     * @param <T>
     * @return
     */
    public static <T> List<T> buildList(Class<T> beanClass, List objs){
        List beans = new ArrayList(objs.size());
        for(Object data: objs){
            beans.add(build(beanClass, data));
        }
        return beans;
    }

    /**
     * 批量转换
     * @param beanClass
     * @param objs
     * @param <T>
     * @return
     */
    public static <T> DbList<T> buildDbList(Class<T> beanClass, DbList objs){
        List beans = new ArrayList(objs.size());
        for(Object data: objs){
            beans.add(build(beanClass, data));
        }
        return new DbList<>(beans, objs.action);
    }

    /**
     * 将Bean的字段与一个model联系起来
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public static @interface Link{

        /**
         * model类
         * @return
         */
        Class<? extends JPABase> value();

        /**
         * model的id来源，将用于调用其findById()
         * @return
         */
        String id();

        /**
         * 将model的字段填充进来
         * @return
         */
        String field() default "";
    }

    /**
     * 将Bean的方法和数值联系起来
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public static @interface Method{

        String value();

        Class as() default Object.class;

    }


}